<!doctype html>
<html class="no-js" lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>
    
  2.4. Map - HashSet & HashMap 源码解析 - 
  
  </title>
 <meta name="description" content="">
 <link href="atom.xml" rel="alternate" title="" type="application/atom+xml">
    <link rel="stylesheet" href="asset/css/foundation.min.css" />
    <link rel="stylesheet" href="asset/css/docs.css" />

    <script src="asset/js/vendor/modernizr.js"></script>
    <script src="asset/js/vendor/jquery.js"></script>
    <script src="asset/highlightjs/highlight.pack.js"></script>
    <link href="asset/highlightjs/styles/github.css" media="screen, projection" rel="stylesheet" type="text/css">
    <script>hljs.initHighlightingOnLoad();</script>
    
  </head>
  <body class="antialiased hide-extras">
    
    <div class="marketing off-canvas-wrap" data-offcanvas>
      <div class="inner-wrap">


<nav class="top-bar docs-bar hide-for-small" data-topbar>

<div id="header">
    <h1><a href="index.html"></a></h1>
</div>

</nav>
        <nav class="tab-bar show-for-small">
  <a href="javascript:void(0)" class="left-off-canvas-toggle menu-icon">
    <span> &nbsp; </span>
  </a>
</nav>

<aside class="left-off-canvas-menu">
      <ul class="off-canvas-list">
      <li><a href="index.html">Home</a></li>
      
        <li class="divider"></li>
        <li><label>1. Java 基础</label></li>

          
            <li><a title="1. Java 基础" href="16923474457318.html">1. Java 基础</a></li>
          

      
        <li class="divider"></li>
        <li><label>2. JCF（Java 集合）</label></li>

          
            <li><a title="2. JCF（Java 集合）" href="16923736135980.html">2. JCF（Java 集合）</a></li>
          
            <li><a title="2.4. Map - HashSet & HashMap 源码解析" href="16924335832240.html">2.4. Map - HashSet & HashMap 源码解析</a></li>
          
            <li><a title="2.5. Map - LinkedHashSet & LinkedHashMap源码解析" href="16924372183713.html">2.5. Map - LinkedHashSet & LinkedHashMap源码解析</a></li>
          
            <li><a title="2.6. Map - TreeSet & TreeMap 源码解析" href="16925007307182.html">2.6. Map - TreeSet & TreeMap 源码解析</a></li>
          

      
        <li class="divider"></li>
        <li><label>mysql</label></li>

          
            <li><a title="事务隔离" href="16925938083498.html">事务隔离</a></li>
          

      
        <li class="divider"></li>
        <li><label>常见文档</label></li>

          
            <li><a title="Paxos选举算法" href="16926055818559.html">Paxos选举算法</a></li>
          

      
      </ul>
    </aside>

<a class="exit-off-canvas" href="#"></a>

        <section id="main-content" role="main" class="scroll-container">

          <div class="row">
            <div class="large-3 medium-3 columns">
              <div class="hide-for-small">
                <div class="sidebar">
                <nav>
                  <ul id="side-nav" class="side-nav">

                    
                      <li class="side-title"><span>1. Java 基础</span></li>
                        
                          <li><a title="1. Java 基础" href="16923474457318.html">1. Java 基础</a></li>
                        

                    
                      <li class="side-title"><span>2. JCF（Java 集合）</span></li>
                        
                          <li><a title="2. JCF（Java 集合）" href="16923736135980.html">2. JCF（Java 集合）</a></li>
                        
                          <li><a title="2.4. Map - HashSet & HashMap 源码解析" href="16924335832240.html">2.4. Map - HashSet & HashMap 源码解析</a></li>
                        
                          <li><a title="2.5. Map - LinkedHashSet & LinkedHashMap源码解析" href="16924372183713.html">2.5. Map - LinkedHashSet & LinkedHashMap源码解析</a></li>
                        
                          <li><a title="2.6. Map - TreeSet & TreeMap 源码解析" href="16925007307182.html">2.6. Map - TreeSet & TreeMap 源码解析</a></li>
                        

                    
                      <li class="side-title"><span>mysql</span></li>
                        
                          <li><a title="事务隔离" href="16925938083498.html">事务隔离</a></li>
                        

                    
                      <li class="side-title"><span>常见文档</span></li>
                        
                          <li><a title="Paxos选举算法" href="16926055818559.html">Paxos选举算法</a></li>
                        

                    
                  </ul>
                </nav>
                </div>
              </div>
            </div>
            <div class="large-9 medium-9 columns">

 <div class="markdown-body">
<h1>2.4. Map - HashSet & HashMap 源码解析</h1>

<ul>
<li>
<a href="#toc_0">2.4.1. Java7 HashMap</a>
<ul>
<li>
<a href="#toc_1">get()</a>
</li>
<li>
<a href="#toc_2">remove()</a>
</li>
</ul>
</li>
<li>
<a href="#toc_3">2.4.2. Java8 HashMap</a>
<ul>
<li>
<a href="#toc_4">数据结构</a>
</li>
<li>
<a href="#toc_5">put 过程分析</a>
</li>
<li>
<a href="#toc_6">数组扩容</a>
</li>
<li>
<a href="#toc_7">get 过程分析</a>
</li>
</ul>
</li>
<li>
<a href="#toc_8">2.4.3. HashSet</a>
</li>
</ul>


<h2 id="toc_0">2.4.1. Java7 HashMap</h2>

<p>之所以把HashSet和HashMap放在一起讲解，是因为二者在Java里有着相同的实现，前者仅仅是对后者做了一层包装，也就是说HashSet里面有一个HashMap(适配器模式)。<br/>
因此本文将重点分析HashMap。HashMap实现了Map接口，即允许放入key为null的元素，也允许插入value为null的元素；除该类未实现同步外，其余跟Hashtable大致相同；跟TreeMap不同，该容器不保证元素顺序，根据需要该容器可能会对元素重新哈希，元素的顺序也会被重新打散，因此不同时间迭代同一个HashMap的顺序可能会不同。 <br/>
根据对冲突的处理方式不同，哈希表有两种实现方式，一种<B>开放地址方式(Open addressing)</B>，另一种是<B>冲突链表方式(Separate chaining with linked lists)。Java7 HashMap采用的是冲突链表方式。</B><br/>
<img src="media/16924335832240/16924339139139.jpg" alt=""/></p>

<p>从上图容易看出，如果选择合适的哈希函数，put() 和 get() 方法可以在常数时间内完成。<br/>
但在对HashMap进行迭代时，需要遍历整个table以及后面跟的冲突链表。因此对于迭代比较频繁的场景，不宜将HashMap的初始大小设的过大。<br/>
有两个参数可以影响HashMap的性能: 初始容量(inital capacity)和负载系数(load factor)。<br/>
初始容量指定了初始table的大小，负载系数用来指定自动扩容的临界值。<br/>
当entry的数量超过capacity*load_factor时，容器将自动扩容并重新哈希。对于插入元素较多的场景，将初始容量设大可以减少重新哈希的次数。<br/>
将对象放入到HashMap或HashSet中时，有两个方法需要特别关心: hashCode()和equals()。<br/>
hashCode()方法决定了对象会被放到哪个bucket里，当多个对象的哈希值冲突时，equals()方法决定了这些对象是否是“同一个对象”。所以，如果要将自定义的对象放入到HashMap或HashSet中，需要 <B>重写 hashCode()和equals()</B>方法。</p>

<h3 id="toc_1">get()</h3>

<p>put(K key, V value) 方法是将指定的 key, value 对添加到 map 里。该方法首先会对 map 做一次查找，看是否包含该元组，如果已经包含则直接返回，查找过程类似于 getEntry() 方法；如果没有找到，则会通过 addEntry(int hash, K key, V value, int bucketIndex) 方法插入新的 entry ，插入方式为头插法。</p>

<p><img src="media/16924335832240/16924342597983.jpg" alt=""/></p>

<pre><code>void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size &gt;= threshold) &amp;&amp; (null != table[bucketIndex])) {
        resize(2 * table.length);//自动扩容，并重新哈希
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = hash &amp; (table.length-1);//hash%table.length
    }
    //在冲突链表头部插入新的entry
    Entry&lt;K,V&gt; e = table[bucketIndex];
    table[bucketIndex] = new Entry&lt;&gt;(hash, key, value, e);
    size++;
}
</code></pre>

<h3 id="toc_2">remove()</h3>

<p>remove(Object key) 的作用是删除 key 值对应的 entry，该方法的具体逻辑是在 removeEntryForKey(Object key) 里实现的。removeEntryForKey() 方法会首先找到 key 值对应的entry，然后删除该 entry (修改链表的相应引用)。查找过程跟 getEntry() 过程类似。</p>

<p><img src="media/16924335832240/16924343386401.jpg" alt=""/></p>

<pre><code>final Entry&lt;K,V&gt; removeEntryForKey(Object key) {
    ......
    int hash = (key == null) ? 0 : hash(key);
    int i = indexFor(hash, table.length);//hash&amp;(table.length-1)
    Entry&lt;K,V&gt; prev = table[i];//得到冲突链表
    Entry&lt;K,V&gt; e = prev;
    while (e != null) {//遍历冲突链表
        Entry&lt;K,V&gt; next = e.next;
        Object k;
        if (e.hash == hash &amp;&amp;
            ((k = e.key) == key || (key != null &amp;&amp; key.equals(k)))) {//找到要删除的entry
            modCount++; size--;
            if (prev == e) table[i] = next;//删除的是冲突链表的第一个entry
            else prev.next = next;
            return e;
        }
        prev = e; e = next;
    }
    return e;
}
</code></pre>

<h2 id="toc_3">2.4.2. Java8 HashMap</h2>

<p>Java8 对 HashMap 进行了一些修改，最大的不同就是利用了红黑树，所以其由 数组+链表+红黑树 组成。<br/>
根据 Java7 HashMap 的介绍，我们知道，查找的时候，根据 hash 值我们能够快速定位到数组的具体下标，但是之后的话，需要顺着链表一个个比较下去才能找到我们需要的，时间复杂度取决于链表的长度，为 O(n)。<br/>
为了降低这部分的开销，在 Java8 中，当链表中的元素达到了 8 个时，会将链表转换为红黑树，在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。</p>

<p><img src="media/16924335832240/16924344768271.jpg" alt=""/></p>

<p>Java8 的源码可读性要差一些，不过精简一些。Java7 中使用 Entry 来代表每个 HashMap 中的数据节点，Java8 中使用 Node，基本没有区别，都是 key，value，hash 和 next 这四个属性，不过，Node 只能用于链表的情况，红黑树的情况需要使用 TreeNode。<br/>
我们根据数组元素中，第一个节点数据类型是 Node 还是 TreeNode 来判断该位置下是链表还是红黑树的。</p>

<h3 id="toc_4">数据结构</h3>

<pre><code>//首层Hash表数组
transient Node&lt;K,V&gt;[] table;

//初始的负载因子为0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

//初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 &lt;&lt; 4; // aka 16

</code></pre>

<h3 id="toc_5">put 过程分析</h3>

<pre><code>public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

// 第四个参数 onlyIfAbsent 如果是 true，那么只有在不存在该 key 时才会进行 put 操作
// 第五个参数 evict 我们这里不关心
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; p; int n, i;
    // 第一次 put 值的时候，会触发下面的 resize()，类似 java7 的第一次 put 也要初始化数组长度
    // 第一次 resize 和后续的扩容有些不一样，因为这次是数组从 null 初始化到默认的 16 或自定义的初始容量
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 找到具体的数组下标，如果此位置没有值，那么直接初始化一下 Node 并放置在这个位置就可以了
    if ((p = tab[i = (n - 1) &amp; hash]) == null)
        tab[i] = newNode(hash, key, value, null);

    else {// 数组该位置有数据
        Node&lt;K,V&gt; e; K k;
        // 首先，判断该位置的第一个数据和我们要插入的数据，key 是不是&quot;相等&quot;，如果是，取出这个节点
        if (p.hash == hash &amp;&amp;
            ((k = p.key) == key || (key != null &amp;&amp; key.equals(k))))
            e = p;
        // 如果该节点是代表红黑树的节点，调用红黑树的插值方法，本文不展开说红黑树
        else if (p instanceof TreeNode)
            e = ((TreeNode&lt;K,V&gt;)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 到这里，说明数组该位置上是一个链表
            for (int binCount = 0; ; ++binCount) {
                // 插入到链表的最后面(Java7 是插入到链表的最前面)
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // TREEIFY_THRESHOLD 为 8，所以，如果新插入的值是链表中的第 8 个
                    // 会触发下面的 treeifyBin，也就是将链表转换为红黑树
                    if (binCount &gt;= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 如果在该链表中找到了&quot;相等&quot;的 key(== 或 equals)
                if (e.hash == hash &amp;&amp;
                    ((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))
                    // 此时 break，那么 e 为链表中[与要插入的新值的 key &quot;相等&quot;]的 node
                    break;
                p = e;
            }
        }
        // e!=null 说明存在旧值的key与要插入的key&quot;相等&quot;
        // 对于我们分析的put操作，下面这个 if 其实就是进行 &quot;值覆盖&quot;，然后返回旧值
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    // 如果 HashMap 由于新插入这个值导致 size 已经超过了阈值，需要进行扩容
    if (++size &gt; threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
</code></pre>

<h3 id="toc_6">数组扩容</h3>

<p>resize() 方法用于初始化数组或数组扩容，每次扩容后，容量为原来的 2 倍，并进行数据迁移。</p>

<pre><code>final Node&lt;K,V&gt;[] resize() {
    Node&lt;K,V&gt;[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap &gt; 0) { // 对应数组扩容
        if (oldCap &gt;= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 将数组大小扩大一倍
        else if ((newCap = oldCap &lt;&lt; 1) &lt; MAXIMUM_CAPACITY &amp;&amp;
                 oldCap &gt;= DEFAULT_INITIAL_CAPACITY)
            // 将阈值扩大一倍
            newThr = oldThr &lt;&lt; 1; // double threshold
    }
    else if (oldThr &gt; 0) // 对应使用 new HashMap(int initialCapacity) 初始化后，第一次 put 的时候
        newCap = oldThr;
    else {// 对应使用 new HashMap() 初始化后，第一次 put 的时候
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }

    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap &lt; MAXIMUM_CAPACITY &amp;&amp; ft &lt; (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;

    // 用新的数组大小初始化新的数组
    Node&lt;K,V&gt;[] newTab = (Node&lt;K,V&gt;[])new Node[newCap];
    table = newTab; // 如果是初始化数组，到这里就结束了，返回 newTab 即可

    if (oldTab != null) {
        // 开始遍历原数组，进行数据迁移。
        for (int j = 0; j &lt; oldCap; ++j) {
            Node&lt;K,V&gt; e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 如果该数组位置上只有单个元素，那就简单了，简单迁移这个元素就可以了
                if (e.next == null)
                    newTab[e.hash &amp; (newCap - 1)] = e;
                // 如果是红黑树，具体我们就不展开了
                else if (e instanceof TreeNode)
                    ((TreeNode&lt;K,V&gt;)e).split(this, newTab, j, oldCap);
                else { 
                    // 这块是处理链表的情况，
                    // 需要将此链表拆成两个链表，放到新的数组中，并且保留原来的先后顺序
                    // loHead、loTail 对应一条链表，hiHead、hiTail 对应另一条链表，代码还是比较简单的
                    Node&lt;K,V&gt; loHead = null, loTail = null;
                    Node&lt;K,V&gt; hiHead = null, hiTail = null;
                    Node&lt;K,V&gt; next;
                    do {
                        next = e.next;
                        if ((e.hash &amp; oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        // 第一条链表
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        // 第二条链表的新的位置是 j + oldCap，这个很好理解
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
</code></pre>

<h3 id="toc_7">get 过程分析</h3>

<p>相对于 put 来说，get 简单一些。<br/>
- 计算 key 的 hash 值，根据 hash 值找到对应数组下标: hash &amp; (length-1)<br/>
- 判断数组该位置处的元素是否刚好就是我们要找的，如果不是，继续下一步<br/>
- 判断该元素类型是否是 TreeNode，如果是，用红黑树的方法取数据，如果不是，继续下一步<br/>
- 遍历链表，直到找到相等(==或equals)的 key</p>

<pre><code>public V get(Object key) {
    Node&lt;K,V&gt; e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node&lt;K,V&gt; getNode(int hash, Object key) {
    Node&lt;K,V&gt;[] tab; Node&lt;K,V&gt; first, e; int n; K k;
    if ((tab = table) != null &amp;&amp; (n = tab.length) &gt; 0 &amp;&amp;
        (first = tab[(n - 1) &amp; hash]) != null) {
        // 判断第一个节点是不是就是需要的
        if (first.hash == hash &amp;&amp; // always check first node
            ((k = first.key) == key || (key != null &amp;&amp; key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            // 判断是否是红黑树
            if (first instanceof TreeNode)
                return ((TreeNode&lt;K,V&gt;)first).getTreeNode(hash, key);

            // 链表遍历
            do {
                if (e.hash == hash &amp;&amp;
                    ((k = e.key) == key || (key != null &amp;&amp; key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
</code></pre>

<h2 id="toc_8">2.4.3. HashSet</h2>

<p>前面已经说过HashSet是对HashMap的简单包装，对HashSet的函数调用都会转换成合适的HashMap方法，因此HashSet的实现非常简单，只有不到300行代码。这里不再赘述。</p>

<pre><code>//HashSet是对HashMap的简单包装
public class HashSet&lt;E&gt;
{
    ......
    private transient HashMap&lt;E,Object&gt; map;//HashSet里面有一个HashMap
    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    public HashSet() {
        map = new HashMap&lt;&gt;();
    }
    ......
    public boolean add(E e) {//简单的方法转换
        return map.put(e, PRESENT)==null;
    }
    ......
}
</code></pre>

<p>默认初始容量&amp;允许的最大长度<br/>
首次扩容时间&amp;扩容逻辑<br/>
哪些地方会发生扩容？调用了什么方法<br/>
底层数据结构<br/>
自定义初始容量<br/>
同步机制<br/>
Fail-Fast<br/>
并发场景下怎么使用<br/>
关于性能：<br/>
    读/查找/修改/随机插入的性能：<br/>
    写入性能：</p>


</div>

<br /><br />
<hr />

<div class="row clearfix">
  <div class="large-6 columns">
	<div class="text-left" style="padding:15px 0px;">
		
	        <a href="16923736135980.html"  title="Previous Post: 2. JCF（Java 集合）">&laquo; 2. JCF（Java 集合）</a>
	    
	</div>
  </div>
  <div class="large-6 columns">
	<div class="text-right" style="padding:15px 0px;">
		
	        <a href="16924372183713.html" 
	        title="Next Post: 2.5. Map - LinkedHashSet & LinkedHashMap源码解析">2.5. Map - LinkedHashSet & LinkedHashMap源码解析 &raquo;</a>
	    
	</div>
  </div>
</div>

<div class="row">
<div style="padding:0px 0.93em;" class="share-comments">

</div>
</div>
<script type="text/javascript">
	$(function(){
		var currentURL = '16924335832240.html';
		$('#side-nav a').each(function(){
			if($(this).attr('href') == currentURL){
				$(this).parent().addClass('active');
			}
		});
	});
</script>  
</div></div>


<div class="page-bottom">
  <div class="row">
  <hr />
  <div class="small-9 columns">
  <p class="copyright">Copyright &copy; 2015
Powered by <a target="_blank" href="http://www.mweb.im">MWeb</a>,&nbsp; 
Theme used <a target="_blank" href="http://github.com">GitHub CSS</a>.</p>
  </div>
  <div class="small-3 columns">
  <p class="copyright text-right"><a href="#header">TOP</a></p>
  </div>
   
  </div>
</div>

        </section>
      </div>
    </div>
    
    
    <script src="asset/js/foundation.min.js"></script>
    <script src="asset/js/foundation/foundation.offcanvas.js"></script>
    <script>
      $(document).foundation();

     
    </script>
    


  </body>
</html>
